﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Budget.Silver.Core.Services;
using Budget.Silver.OutcomeServiceReference;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Input;
using Budget.Silver.Core.Extensions;
using Budget.Silver.Core.Messages;
using Budget.Silver.Model;
using Budget.Silver.Model.Calendar;
using GalaSoft.MvvmLight.Command;
using MvvmValidation;

namespace Budget.Silver.ViewModel
{
    public class OutcomeViewModel : ValidatableViewModel
    {
        #region Private members

        #region Member data

        private bool isNewProduct, isNewCategory;
        private int selectedOrderIndex;
        private readonly OutcomeServiceClient service;
        private readonly CalendarViewModel calendarViewModel;
        private string selectedProductName, selectedCategoryName;
        private CategoryData selectedCategory;
        private ProductData selectedProduct;
        private DateTime selectedDate;
        private Decimal amount;
        private String amountString, editingCategoryName, editingProductName;
        private ObservableCollection<CategoryData> categories;
        private ObservableCollection<OutcomeOrderData> dayDataOrders;
        private ObservableCollection<OutcomeOrderData> monthDataOrders;
        private ObservableCollection<ProductData> products;
        private decimal editingOrderAmout;
        private OutcomeOrderData newOrder;
        private IDialogService dialogService;
        private OrderModel selectedOrder;

        #endregion

        #region Private methods

        private void CalendarViewModelOnSelectDay(object sender, CalendarEventArgs e)
        {
            if (e.SelectedDay != null)
            {
                SelectedDate = e.SelectedDay.Date;
            }
        }

        #region Validation

        protected override void AddValidationRules()
        {
            Validator.AddRequiredRule(() => AmountString, "Required!");
            Validator.AddRule(() => AmountString, () => RuleResult.Assert(AmountString.TryParseToDecimal(out amount), "Is not a number!"));
            Validator.AddRule(() => AmountString, () => RuleResult.Assert(amount > 0, "Incorrect amount!"));
        }

        #endregion

        #region Completed methods

        private void AddCompletedEvents()
        {
            service.GetAllCategoriesCompleted += ServiceOnGetAllCategoriesCompleted;
            service.AddOutcomeOrderCompleted += ServiceOnAddOutcomeOrderCompleted;
            service.GetMonthOutcomesCompleted += ServiceOnGetMonthOutcomesCompleted;
            service.DeleteOutcomeOrderCompleted += ServiceAsyncOperationCompleted;
            service.UpdateOutcomeOrderCompleted += ServiceAsyncOperationCompleted;
            service.UpdateCategoryCompleted += ServiceAsyncOperationCompleted;
            service.UpdateProductCompleted += ServiceAsyncOperationCompleted;
        }

        private void ServiceAsyncOperationCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessengerInstance.Send(new ExceptionMessage(e.Error));
            }
            MessengerInstance.Send(new BusyMessage(false));
        }

        private void ServiceOnGetMonthOutcomesCompleted(object sender, GetMonthOutcomesCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                monthDataOrders = e.Result;
                dayDataOrders = new ObservableCollection<OutcomeOrderData>(monthDataOrders.Where(o => o.CreationDate.Date == SelectedDate.Date));
                calendarViewModel.SelectMonthAndDate(monthDataOrders, SelectedDate);
                SelectedOrderIndex = -1;
                RaisePropertyChanged(() => Orders);
                RaisePropertyChanged(() => DaySummary);
            }
            else
            {
                MessengerInstance.Send(new ExceptionMessage(e.Error));
            }
            MessengerInstance.Send(new BusyMessage(false));
        }

        private void ServiceOnGetAllCategoriesCompleted(object sender, GetAllCategoriesCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                Categories = e.Result;
            }
            else
            {
                MessengerInstance.Send(new ExceptionMessage(e.Error));
            }

            MessengerInstance.Send(new BusyMessage(false));
        }

        private void ServiceOnAddOutcomeOrderCompleted(object sender, AddOutcomeOrderCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                newOrder.Id = e.Result.OutcomeOrderId;
                if (isNewCategory)
                {
                    var newCategory = new CategoryData {Id = e.Result.CategoryId, Name = SelectedCategoryName, Products = new ObservableCollection<ProductData>()};
                    categories.Add(newCategory);
                    RaisePropertyChanged(() => Categories);
                    selectedCategory = newCategory;
                    isNewCategory = false;
                }
                if (isNewProduct)
                {
                    var p = new ProductData { Id = e.Result.ProductId, Name = SelectedProductName, Category = SelectedCategory };
                    SelectedCategory.Products.Add(p);
                    RaisePropertyChanged(() => Products);
                    isNewProduct = false;
                }
                RaisePropertyChanged(() => SelectedCategory);
                UpdateOrders();
                Clear();
            }
            else
            {
                MessengerInstance.Send(new ExceptionMessage(e.Error));
            }

            MessengerInstance.Send(new BusyMessage(false));
        }

        #endregion
        
        #region Commands

        private void CreateCommands()
        {
            PressEnter = new RelayCommand<KeyEventArgs>(PressEnterCommand);
            AddOrder = new RelayCommand(AddOrderCommand);
            SelectCategory = new RelayCommand<SelectionChangedEventArgs>(SelectCategoryCommand);
            SelectProduct = new RelayCommand<SelectionChangedEventArgs>(SelectProductCommand);
            EditCategory = new RelayCommand<DataGridCellEditEndedEventArgs>(EditCategoryCommand);
            BeginEditCategory = new RelayCommand<DataGridBeginningEditEventArgs>(BeginEditCategoryCommand);
            EditProduct = new RelayCommand<DataGridCellEditEndedEventArgs>(EditProductCommand);
            BeginEditProduct = new RelayCommand<DataGridBeginningEditEventArgs>(BeginEditProductCommand);
            EditOrder = new RelayCommand<DataGridCellEditEndedEventArgs>(EditOrderCommand);
            BeginEditOrder = new RelayCommand<DataGridBeginningEditEventArgs>(BeginEditOrderCommand);
            DeleteOrder = new RelayCommand<KeyEventArgs>(DeleteOrderCommand);
        }

        private void DeleteOrderCommand(KeyEventArgs e)
        {
            if (e.Key == Key.Delete)
            {
                dialogService.Show("Подтверждение удаления!", "Вы уверены, что хотите удалить выделенный расход?",
                                   result =>
                                       {
                                           if (result.HasValue && result.Value)
                                           {
                                               var orderData = dayDataOrders.First(o => o.Id == SelectedOrder.Id);
                                               service.DeleteOutcomeOrderAsync(orderData);
                                               MessengerInstance.Send(new BusyMessage(true));

                                               dayDataOrders.Remove(orderData);
                                               monthDataOrders.Remove(orderData);
                                               UpdateOrders();
                                           }
                                       });
            }
        }

        private void BeginEditProductCommand(DataGridBeginningEditEventArgs e)
        {
            var product = e.Row.DataContext as ProductData;
            editingProductName = product.Name;
        }

        private void EditProductCommand(DataGridCellEditEndedEventArgs e)
        {
            if (e.EditAction == DataGridEditAction.Commit)
            {
                var product = e.Row.DataContext as ProductData;
                if (product.Name != editingProductName)
                {
                    service.UpdateProductAsync(product);
                    MessengerInstance.Send(new BusyMessage(true));
                    RaisePropertyChanged(() => Products);

                    UpdateOrders();
                }
            }
        }

        private void BeginEditCategoryCommand(DataGridBeginningEditEventArgs e)
        {
            var category = e.Row.DataContext as CategoryData;
            editingCategoryName = category.Name;
        }

        private void EditCategoryCommand(DataGridCellEditEndedEventArgs e)
        {
            if (e.EditAction == DataGridEditAction.Commit)
            {
                var category = e.Row.DataContext as CategoryData;
                if (category.Name != editingCategoryName)
                {
                    service.UpdateCategoryAsync(category);
                    MessengerInstance.Send(new BusyMessage(true));
                    RaisePropertyChanged(() => Categories);

                    UpdateOrders();
                }
            }
        }

        private void BeginEditOrderCommand(DataGridBeginningEditEventArgs e)
        {
            var order = e.Row.DataContext as OrderModel;
            editingOrderAmout = order.Amount;
        }

        private void EditOrderCommand(DataGridCellEditEndedEventArgs e)
        {
            if (e.EditAction == DataGridEditAction.Commit)
            {
                var order = e.Row.DataContext as OrderModel;
                if (order.Amount != editingOrderAmout)
                {
                    var updatedOrder = dayDataOrders.First(o => o.Id == order.Id);
                    updatedOrder.Amount = order.Amount;
                    service.UpdateOutcomeOrderAsync(updatedOrder);
                    MessengerInstance.Send(new BusyMessage(true));

                    UpdateOrders();
                }
            }
        }

        private void PressEnterCommand(KeyEventArgs e)
        {
            if (e.Key == Key.Enter && IsReady)
            {
                AddOrderCommand();
            }
        }

        private void SelectCategoryCommand(SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count > 0)
            {
                var category = e.AddedItems[0] as CategoryData;

                if (category != null)
                {
                    SelectedCategoryName = category.Name;
                }
            }
        }

        private void SelectProductCommand(SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count > 0)
            {
                var product = e.AddedItems[0] as ProductData;

                if (product != null)
                {
                    SelectedProductName = product.Name;
                }
            }
        }

        private void AddOrderCommand()
        {
            ProductData product;
            var category = Categories.FirstOrDefault(c => c.Name == SelectedCategoryName);
            if (category == null)
            {
                category = new CategoryData {Name = SelectedCategoryName};
                product = new ProductData {Name = SelectedProductName, Category = category};
                isNewCategory = isNewProduct = true;
            }
            else if (SelectedCategory.Products.Any(p => p.Name == SelectedProductName))
            {
                product = SelectedCategory.Products.First(p => p.Name == SelectedProductName);
            }
            else
            {
                product = new ProductData { Name = SelectedProductName, Category = category };
                isNewProduct = true;
            }

            newOrder = CreateOrder(product);
            dayDataOrders.Add(newOrder);
            monthDataOrders.Add(newOrder);

            service.AddOutcomeOrderAsync(newOrder);
            MessengerInstance.Send(new BusyMessage(true));
        }

        #endregion

        #region Member methods

        private void Clear()
        {
            amount = 0;
            SelectedProductName = amountString = String.Empty;
            RaisePropertyChanged(() => AmountString);
            RaisePropertyChanged(() => IsReady);
        }

        private void UpdateOrders()
        {
            calendarViewModel.SelectMonthAndDate(monthDataOrders, SelectedDate);
            SelectedOrderIndex = -1;
            RaisePropertyChanged(() => Orders);
            RaisePropertyChanged(() => DaySummary);
        }

        private OutcomeOrderData CreateOrder(ProductData product)
        {
            var order = new OutcomeOrderData
                            {
                                CreationDate = SelectedDate,
                                Amount = amount,
                                Product = product
                            };
            return order;
        }

        #endregion

        #endregion

        #endregion

        #region Public members

        #region Binding data

        public MonthModel Month { get; set; }

        public ObservableCollection<CategoryData> Categories
        {
            get { return categories.SortBy(c => c.Name); }
            set
            {
                categories = value;
                RaisePropertyChanged(() => Categories);
            }
        }

        public ObservableCollection<ProductData> Products
        {
            get { return SelectedCategory != null ? SelectedCategory.Products.SortBy(p => p.Name) : null; }
        }

        public CategoryData SelectedCategory
        {
            get { return selectedCategory; }
            set
            {
                if (value != null && !Equals(selectedCategory, value))
                {
                    selectedCategory = value;
                    selectedCategoryName = selectedCategory.Name;
                    SelectedProductName = string.Empty;

                    RaisePropertyChanged(() => SelectedCategory);
                    RaisePropertyChanged(() => SelectedCategoryName);
                    RaisePropertyChanged(() => Products);
                }
            }
        }

        public ProductData SelectedProduct
        {
            get { return selectedProduct; }
            set
            {
                if (!Equals(selectedProduct, value))
                {
                    selectedProduct = value;
                    RaisePropertyChanged(() => SelectedProduct);
                }
            }
        }

        public OrderModel SelectedOrder
        {
            get { return selectedOrder; }
            set
            {
                if (!Equals(selectedOrder, value))
                {
                    selectedOrder = value;
                    RaisePropertyChanged(() => SelectedOrder);
                }
            }
        }

        public string SelectedCategoryName
        {
            get { return selectedCategoryName; }
            set
            {
                if (!Equals(selectedCategoryName, value))
                {
                    selectedCategoryName = value;

                    RaisePropertyChanged(() => SelectedCategoryName);
                }
            }
        }

        public string SelectedProductName 
        {
            get { return selectedProductName; }
            set 
            {
                if (!Equals(selectedProductName, value))
                {
                    selectedProductName = value;

                    RaisePropertyChanged(() => SelectedProductName);
                }
            }
        }

        public DateTime SelectedDate
        {
            get { return selectedDate; }
            set
            {
                if (selectedDate != value)
                {
                    if (selectedDate.Month != value.Month)
                    {
                        service.GetMonthOutcomesAsync(value.Year, value.Month);
                        MessengerInstance.Send(new BusyMessage(true));
                    }
                    else
                    {
                        dayDataOrders = new ObservableCollection<OutcomeOrderData>(monthDataOrders.Where(o => o.CreationDate.Date == value.Date));
                        calendarViewModel.SelectDate(value);
                        SelectedOrderIndex = -1;
                        RaisePropertyChanged(() => Orders);
                        RaisePropertyChanged(() => DaySummary);
                    }
                    selectedDate = value;
                    RaisePropertyChanged(() => SelectedDate);
                }
            }
        }

        public ICollection<OutcomeOrderModel> Orders
        {
            get { return OutcomeOrderModel.CreateOrders(dayDataOrders); }
        }

        public int SelectedOrderIndex
        {
            get { return selectedOrderIndex; }
            set
            {
                if (selectedOrderIndex != value)
                {
                    selectedOrderIndex = value;

                    RaisePropertyChanged(() => SelectedOrderIndex);
                }
            }
        }

        public decimal DaySummary 
        {
            get { return Orders.Sum(o => o.CategorySum); }
        }

        public String AmountString
        {
            get { return amountString; }
            set
            {
                if (!Equals(amountString, value))
                {
                    amountString = value;

                    amount = 0;
                    if (!amountString.TryParseToDecimal(out amount))
                    {
                        RaisePropertyChanged(() => IsReady);
                    }

                    ValidateAll();

                    amount = amountString.ParseToDecimal();

                    RaisePropertyChanged(() => IsReady);
                }
            }
        }

        public Boolean IsReady
        {
            get { return !string.IsNullOrEmpty(SelectedCategoryName) && !string.IsNullOrEmpty(SelectedProductName) && amount > 0; }
        }

        public ICommand AddOrder { get; private set; }

        public ICommand PressEnter { get; private set; }

        public ICommand SelectCategory { get; private set; }

        public ICommand BeginEditCategory { get; private set; }

        public ICommand EditCategory { get; private set; }

        public ICommand BeginEditProduct { get; private set; }

        public ICommand EditProduct { get; private set; }

        public ICommand BeginEditOrder { get; private set; }

        public ICommand EditOrder { get; private set; }

        public ICommand DeleteOrder { get; private set; }

        public ICommand SelectProduct { get; private set; }

        #endregion

        #region Public methods

        
        public OutcomeViewModel(OutcomeServiceClient service, CalendarViewModel calendarViewModel, IDialogService dialogService)
        {
            this.service = service;
            monthDataOrders = new ObservableCollection<OutcomeOrderData>();
            dayDataOrders = new ObservableCollection<OutcomeOrderData>();
            this.calendarViewModel = calendarViewModel;
            this.dialogService = dialogService;
            dialogService.Width = 300;
            dialogService.Height = 200;
            this.calendarViewModel.SelectDay += CalendarViewModelOnSelectDay;
            SelectedDate = DateTime.Now;
            CreateCommands();
            AddCompletedEvents();
            service.GetAllCategoriesAsync();
            service.GetMonthOutcomesAsync(SelectedDate.Year, SelectedDate.Month);
            MessengerInstance.Send(new BusyMessage(true));
        }

        #endregion

        #endregion
    }
}